<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 4.2.1">
  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/%E6%AD%A6%E6%B1%8932x32.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/%E6%AD%A6%E6%B1%8916x16.png">
  <link rel="mask-icon" href="/images/logo.svg" color="#222">

<link rel="stylesheet" href="/css/main.css">


<link rel="stylesheet" href="/lib1/font-awesome/css/all.min.css">
  <link rel="stylesheet" href="/lib1/pace/pace-theme-center-simple.min.css">
  <script src="/lib1/pace/pace.min.js"></script>

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"example.com","root":"/","scheme":"Gemini","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":true,"show_result":true,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":true,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},"path":"search.xml"};
  </script>

  <meta name="description" content="前言最近由于以后入职技术栈的原因开始看Rax与Mobx，对于一个之前更偏向Vue技术栈的我，Mobx总有一个似曾相识的感觉，总感觉这不就是在React的JSX中使用Vue的双向绑定吗？虽说Mobx说它们的源码实现不是用的上世纪的发布订阅模式，但我还是表示怀疑。同样总感觉前端方面的状态管理框架、模式层出不穷，各自有各自的特点，还有各自的Api，属实是有点被搞混乱了，因此，这篇文章大概就是想对目前自己">
<meta property="og:type" content="article">
<meta property="og:title" content="前端状态管理总结">
<meta property="og:url" content="http://example.com/2021/12/06/%E7%9B%92%E9%A9%AC%E6%8A%80%E6%9C%AF%E6%A0%88%E5%88%9D%E5%AD%A6/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/index.html">
<meta property="og:site_name" content="Technological Blog">
<meta property="og:description" content="前言最近由于以后入职技术栈的原因开始看Rax与Mobx，对于一个之前更偏向Vue技术栈的我，Mobx总有一个似曾相识的感觉，总感觉这不就是在React的JSX中使用Vue的双向绑定吗？虽说Mobx说它们的源码实现不是用的上世纪的发布订阅模式，但我还是表示怀疑。同样总感觉前端方面的状态管理框架、模式层出不穷，各自有各自的特点，还有各自的Api，属实是有点被搞混乱了，因此，这篇文章大概就是想对目前自己">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="http://example.com/image/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/dusenr00sr.jpeg">
<meta property="og:image" content="http://example.com/image/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/2u3j39osyq.jpeg">
<meta property="og:image" content="http://example.com/image/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/bio4rizpf5.jpeg">
<meta property="og:image" content="http://example.com/image/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/bio4rizpf5%20(1).jpeg">
<meta property="og:image" content="http://example.com/image/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/71dp1uvs0t.jpeg">
<meta property="og:image" content="http://example.com/image/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/517d1ncf2c.jpeg">
<meta property="og:image" content="http://example.com/image/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/7e4tfmk7of.jpeg">
<meta property="og:image" content="http://example.com/image/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/7mhhp1q702.jpeg">
<meta property="og:image" content="http://example.com/image/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/2az1mka48t.jpeg">
<meta property="og:image" content="http://example.com/image/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/w3otvvj866.jpeg">
<meta property="og:image" content="http://example.com/image/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/q5b4n6r4nt.jpeg">
<meta property="og:image" content="http://example.com/image/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/pj9kabuq19.jpeg">
<meta property="og:image" content="http://example.com/image/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/hn7fil8nbb.jpeg">
<meta property="og:image" content="http://example.com/image/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/egue4r5wnl.gif">
<meta property="article:published_time" content="2021-12-06T04:18:39.000Z">
<meta property="article:modified_time" content="2021-12-06T03:08:01.129Z">
<meta property="article:author" content="Li Yudong">
<meta property="article:tag" content="公司技术栈">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="http://example.com/image/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/dusenr00sr.jpeg">

<link rel="canonical" href="http://example.com/2021/12/06/%E7%9B%92%E9%A9%AC%E6%8A%80%E6%9C%AF%E6%A0%88%E5%88%9D%E5%AD%A6/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : false,
    isPost : true,
    lang   : 'zh-CN'
  };
</script>

  <title>前端状态管理总结 | Technological Blog</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">Technological Blog</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
      <p class="site-subtitle" itemprop="description">IT小白的成长之旅</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
        <i class="fa fa-search fa-fw fa-lg"></i>
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-about">

    <a href="/about/" rel="section"><i class="user fa-fw"></i>关于</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/tags/" rel="section"><i class="tags fa-fw"></i>标签<span class="badge">18</span></a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/categories/" rel="section"><i class="th fa-fw"></i>分类<span class="badge">14</span></a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/archives/" rel="section"><i class="archive fa-fw"></i>归档<span class="badge">95</span></a>

  </li>
      <li class="menu-item menu-item-search">
        <a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>搜索
        </a>
      </li>
  </ul>
</nav>



  <div class="search-pop-overlay">
    <div class="popup search-popup">
        <div class="search-header">
  <span class="search-icon">
    <i class="fa fa-search"></i>
  </span>
  <div class="search-input-container">
    <input autocomplete="off" autocapitalize="off"
           placeholder="搜索..." spellcheck="false"
           type="search" class="search-input">
  </div>
  <span class="popup-btn-close">
    <i class="fa fa-times-circle"></i>
  </span>
</div>
<div id="search-result">
  <div id="no-result">
    <i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>
  </div>
</div>

    </div>
  </div>

</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>

  <a href="https://github.com/tiarmor1" class="github-corner" title="Follow me on GitHub" aria-label="Follow me on GitHub" rel="noopener" target="_blank"><svg width="80" height="80" viewBox="0 0 250 250" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content post posts-expand">
            

    
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/12/06/%E7%9B%92%E9%A9%AC%E6%8A%80%E6%9C%AF%E6%A0%88%E5%88%9D%E5%AD%A6/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          前端状态管理总结
        </h1>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
              
    
              <time title="创建时间：2021-12-06 12:18:39 / 修改时间：11:08:01" itemprop="dateCreated datePublished" datetime="2021-12-06T12:18:39+08:00">2021-12-06</time>
            </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E7%9B%92%E9%A9%AC%E6%8A%80%E6%9C%AF%E6%A0%88/" itemprop="url" rel="index"><span itemprop="name">盒马技术栈</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
        <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>最近由于以后入职技术栈的原因开始看Rax与Mobx，对于一个之前更偏向Vue技术栈的我，Mobx总有一个似曾相识的感觉，总感觉这不就是在React的JSX中使用Vue的双向绑定吗？虽说Mobx说它们的源码实现不是用的上世纪的发布订阅模式，但我还是表示怀疑。同样总感觉前端方面的状态管理框架、模式层出不穷，各自有各自的特点，还有各自的Api，属实是有点被搞混乱了，因此，这篇文章大概就是想对目前自己见过、使用过的前端状态管理框架做一个简单总结吧。</p>
<blockquote>
<p><em><strong>这篇文章试着聊明白这一堆看起来挺复杂的东西。在聊之前，大家要始终记得一句话：一切前端概念，都是纸老虎</strong>。</em></p>
</blockquote>
<p>不管是Vue，还是 React，都需要管理状态（state），比如组件之间都有共享状态的需要。什么是共享状态？比如一个组件需要使用另一个组件的状态，或者一个组件需要改变另一个组件的状态，都是共享状态。</p>
<p>父子组件之间，兄弟组件之间共享状态，往往需要写很多没有必要的代码，比如把状态提升到父组件里，或者给兄弟组件写一个父组件，听听就觉得挺啰嗦。如果不对状态进行有效的管理，状态在什么时候，由于什么原因，如何变化就会不受控制，就很难跟踪和测试了。如果没有经历过这方面的困扰，可以简单理解为会搞得很乱就对了。</p>
<p>在软件开发里，有些通用的思想，比如<strong>隔离变化，约定优于配置</strong>等，隔离变化就是说做好抽象，把一些容易变化的地方找到共性，隔离出来，不要去影响其他的代码。约定优于配置就是很多东西我们不一定要写一大堆的配置，比如我们几个人约定，view 文件夹里只能放视图，不能放过滤器，过滤器必须放到 filter 文件夹里，那这就是一种约定，约定好之后，我们就不用写一大堆配置文件了，我们要找所有的视图，直接从 view 文件夹里找就行。</p>
<p>根据这些思想，对于状态管理的解决思路就是：<strong>把组件之间需要共享的状态抽取出来，遵循特定的约定，统一来管理，让状态的变化可以预测。</strong>根据这个思路，产生了很多的模式和库，我们来挨个聊聊。</p>
<h1 id="Store模式"><a href="#Store模式" class="headerlink" title="Store模式"></a>Store模式</h1><p>最简单的处理就是把状态存到一个外部变量里面，比如：this.root.data，当然也可以是一个全局变量。但是这样有一个问题，就是数据改变后，不会留下变更过的记录，这样不利于调试。</p>
<p>所以我们稍微搞得复杂一点，用一个简单的 Store 模式：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> store = &#123;</span><br><span class="line">  state: &#123;</span><br><span class="line">    message: <span class="string">'Hello!'</span></span><br><span class="line">  &#125;,</span><br><span class="line">  setMessageAction (newValue) &#123;</span><br><span class="line">    <span class="comment">// 发生改变记录点日志啥的</span></span><br><span class="line">    <span class="keyword">this</span>.state.message = newValue</span><br><span class="line">  &#125;,</span><br><span class="line">  clearMessageAction () &#123;</span><br><span class="line">    <span class="keyword">this</span>.state.message = <span class="string">''</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>store 的 state 来存数据，store 里面有一堆的 action，这些 action 来控制 state 的改变，也就是不直接去对 state 做改变，而是通过 action 来改变，因为都走 action，我们就可以知道到底改变（mutation）是如何被触发的，出现错误，也可以记录记录日志啥的。</p>
<p><img src="/../../image/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/dusenr00sr.jpeg" alt="Store"></p>
<p>不过这里没有限制组件里面不能修改 store 里面的 state，万一组件瞎胡修改，不通过 action，那我们也没法跟踪这些修改是怎么发生的。所以就需要规定一下，组件不允许直接修改属于 store 实例的 state，组件必须通过 action 来改变 state，也就是说，<strong>组件里面应该执行 action 来分发 (dispatch) 事件通知 store 去改变</strong>。这样约定的好处是，我们能够记录所有 store 中发生的 state 改变，同时实现能做到记录变更 (mutation)、保存状态快照、历史回滚/时光旅行的先进的调试工具。</p>
<p>这样进化了一下，一个简单的 Flux 架构就实现了。</p>
<h1 id="Flux"><a href="#Flux" class="headerlink" title="Flux"></a>Flux</h1><p>Flux其实是一种思想，就像MVC，MVVM之类的，他给出了一些基本概念，所有的框架都可以根据他的思想来做一些实现。</p>
<p>Flux把一个应用分成了4个部分：View Action Dispatcher Store</p>
<p><img src="/../../image/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/2u3j39osyq.jpeg" alt="Flux"></p>
<p>比如我们搞一个应用，显而易见，这个应用里面会有一堆的 View，这个 View 可以是Vue的，也可以是 React的，啥框架都行，啥技术都行。</p>
<p>View 肯定是要展示数据的，所谓的数据，就是 Store，Store 很容易明白，就是存数据的地方。当然我们可以把 Store 都放到一起，也可以分开来放，所以就有一堆的 Store。但是这些 View 都有一个特点，就是 Store 变了得跟着变。</p>
<p>View 怎么跟着变呢？<strong>一般 Store 一旦发生改变，都会往外面发送一个事件，比如 change，通知所有的订阅者。View 通过订阅也好，监听也好，不同的框架有不同的技术，反正 Store 变了，View 就会变。</strong></p>
<p>View 不是光用来看的，一般都会有用户操作，用户点个按钮，改个表单啥的，就需要修改 Store。<strong>Flux 要求，View 要想修改 Store，必须经过一套流程，有点像我们刚才 Store 模式里面说的那样。视图先要告诉 Dispatcher，让 Dispatcher dispatch 一个 action，Dispatcher 就像是个中转站，收到 View 发出的 action，然后转发给 Store。</strong>比如新建一个用户，View 会发出一个叫 addUser 的 action 通过 Dispatcher 来转发，Dispatcher 会把 addUser 这个 action 发给所有的 store，store 就会触发 addUser 这个 action，来更新数据。数据一更新，那么 View 也就跟着更新了。</p>
<p>这个过程有几个需要注意的点：<strong>Dispatcher 的作用是接收所有的 Action，然后发给所有的 Store。</strong>这里的 Action 可能是 View 触发的，也有可能是其他地方触发的，比如测试用例。转发的话也不是转发给某个 Store，而是所有 Store。Store 的改变只能通过 Action，不能通过其他方式。也就是说 Store 不应该有公开的 Setter，所有 Setter 都应该是私有的，只能有公开的 Getter。具体 Action 的处理逻辑一般放在 Store 里。</p>
<p>听听描述看看图，可以发现，<strong>Flux的最大特点就是数据都是单向流动的</strong>。</p>
<h1 id="Redux"><a href="#Redux" class="headerlink" title="Redux"></a><strong>Redux</strong></h1><p>Flux 有一些缺点（特点），比如一个应用可以拥有多个 Store，多个Store之间可能有依赖关系；Store 封装了数据还有处理数据的逻辑。</p>
<p>所以大家在使用的时候，一般会用 Redux，他和 Flux 思想比较类似，也有差别。</p>
<p><img src="/../../image/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/bio4rizpf5.jpeg" alt="Redux"></p>
<h2 id="store"><a href="#store" class="headerlink" title="store"></a>store</h2><p>Redux 里面只有一个 Store，整个应用的数据都在这个大 Store 里面。Store 的 State 不能直接修改，每次只能返回一个新的 State。Redux 整了一个 createStore 函数来生成 Store。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; createStore &#125; <span class="keyword">from</span> <span class="string">'redux'</span>;</span><br><span class="line"><span class="keyword">const</span> store = createStore(fn);</span><br></pre></td></tr></table></figure>

<p>Store 允许使用 store.subscribe 方法设置监听函数，一旦 State 发生变化，就自动执行这个函数。这样不管 View 是用什么实现的，只要把 View 的更新函数 subscribe 一下，就可以实现 State 变化之后，View 自动渲染了。比如在 React 里，把组件的render方法或setState方法订阅进去就行。</p>
<h2 id="Action"><a href="#Action" class="headerlink" title="Action"></a><strong>Action</strong></h2><p>和 Flux 一样，Redux 里面也有 Action，Action 就是 View 发出的通知，告诉 Store State 要改变。Action 必须有一个 type 属性，代表 Action 的名称，其他可以设置一堆属性，作为参数供 State 变更时参考。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> action = &#123;</span><br><span class="line">  type: <span class="string">'ADD_TODO'</span>,</span><br><span class="line">  payload: <span class="string">'Learn Redux'</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>Redux 可以用 Action Creator 批量来生成一些 Action。</p>
<h2 id="Reducer"><a href="#Reducer" class="headerlink" title="Reducer"></a><strong>Reducer</strong></h2><p>Redux 没有 Dispatcher 的概念，Store 里面已经集成了 dispatch 方法。store.dispatch()是 View 发出 Action 的唯一方法。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; createStore &#125; <span class="keyword">from</span> <span class="string">'redux'</span>;</span><br><span class="line"><span class="keyword">const</span> store = createStore(fn);</span><br><span class="line">store.dispatch(&#123;</span><br><span class="line">  type: <span class="string">'ADD_TODO'</span>,</span><br><span class="line">  payload: <span class="string">'Learn Redux'</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>Redux 用一个叫做 Reducer 的纯函数来处理事件。Store 收到 Action 以后，必须给出一个新的 State（就是刚才说的Store 的 State 不能直接修改，每次只能返回一个新的 State），这样 View 才会发生变化。<strong>这种 State 的计算过程就叫做 Reducer。</strong></p>
<p><strong>什么是纯函数呢，就是说没有任何的副作用</strong>，比如这样一个函数：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getAge</span>(<span class="params">user</span>) </span>&#123;</span><br><span class="line">  user.age = user.age + <span class="number">1</span>;</span><br><span class="line">  <span class="keyword">return</span> user.age;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这个函数就有副作用，每一次相同的输入，都可能导致不同的输出，而且还会影响输入 user 的值，再比如：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> b = <span class="number">10</span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">compare</span>(<span class="params">a</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> a &gt;= b;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这个函数也有副作用，就是依赖外部的环境，b 在别处被改变了，返回值对于相同的 a 就有可能不一样。</p>
<p>而 Reducer 是一个纯函数，对于相同的输入，永远都只会有相同的输出，不会影响外部的变量，也不会被外部变量影响，不得改写参数。它的作用大概就是这样，根据应用的状态和当前的 action 推导出新的 state：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(previousState, action) =&gt; newState</span><br></pre></td></tr></table></figure>

<p>类比 Flux，Flux 有些像：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(state, action) =&gt; state</span><br></pre></td></tr></table></figure>

<p>为什么叫做 Reducer 呢？<strong>reduce 是一个函数式编程的概念，经常和 map 放在一起说，简单来说，map 就是映射，reduce 就是归纳。映射就是把一个列表按照一定规则映射成另一个列表，而 reduce 是把一个列表通过一定规则进行合并，也可以理解为对初始值进行一系列的操作，返回一个新的值。</strong></p>
<p>比如 Array 就有一个方法叫 reduce，Array.prototype.reduce(reducer, ?initialValue)，把 Array 整吧整吧弄成一个 newValue。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> array1 = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>];</span><br><span class="line"><span class="keyword">const</span> reducer = <span class="function">(<span class="params">accumulator, currentValue</span>) =&gt;</span> accumulator + currentValue;</span><br><span class="line"><span class="comment">// 1 + 2 + 3 + 4</span></span><br><span class="line"><span class="built_in">console</span>.log(array1.reduce(reducer));</span><br><span class="line"><span class="comment">// expected output: 10</span></span><br><span class="line"><span class="comment">// 5 + 1 + 2 + 3 + 4</span></span><br><span class="line"><span class="built_in">console</span>.log(array1.reduce(reducer, <span class="number">5</span>));</span><br><span class="line"><span class="comment">// expected output: 15</span></span><br></pre></td></tr></table></figure>

<p>看起来和 Redux 的 Reducer 是不是好像好像，<strong>Redux 的 Reducer 就是 reduce 一个列表（action的列表）和一个 initialValue（初始的 State）到一个新的 value（新的 State）</strong>。</p>
<p>把上面的概念连起来，举个例子：</p>
<p>下面的代码声明了 reducer：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> defaultState = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">const</span> reducer = <span class="function">(<span class="params">state = defaultState, action</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">switch</span> (action.type) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">'ADD'</span>:</span><br><span class="line">      <span class="keyword">return</span> state + action.payload;</span><br><span class="line">    <span class="keyword">default</span>: </span><br><span class="line">      <span class="keyword">return</span> state;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>createStore接受 Reducer 作为参数，生成一个新的 Store。以后每当store.dispatch发送过来一个新的 Action，就会自动调用 Reducer，得到新的 State。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; createStore &#125; <span class="keyword">from</span> <span class="string">'redux'</span>;</span><br><span class="line"><span class="keyword">const</span> store = createStore(reducer);</span><br></pre></td></tr></table></figure>

<p>createStore 内部干了什么事儿呢？通过一个简单的 createStore 的实现，可以了解大概的原理（可以略过不看）：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> createStore = <span class="function">(<span class="params">reducer</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">let</span> state;</span><br><span class="line">  <span class="keyword">let</span> listeners = [];</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> getState = <span class="function"><span class="params">()</span> =&gt;</span> state;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> dispatch = <span class="function">(<span class="params">action</span>) =&gt;</span> &#123;</span><br><span class="line">    state = reducer(state, action);</span><br><span class="line">    listeners.forEach(<span class="function"><span class="params">listener</span> =&gt;</span> listener());</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> subscribe = <span class="function">(<span class="params">listener</span>) =&gt;</span> &#123;</span><br><span class="line">    listeners.push(listener);</span><br><span class="line">    <span class="keyword">return</span> <span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">      listeners = listeners.filter(<span class="function"><span class="params">l</span> =&gt;</span> l !== listener);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  dispatch(&#123;&#125;);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> &#123; getState, dispatch, subscribe &#125;;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>Redux 有很多的 Reducer，对于大型应用来说，State 必然十分庞大，导致 Reducer 函数也十分庞大，所以需要做拆分。Redux 里每一个 Reducer 负责维护 State 树里面的一部分数据，多个 Reducer 可以通过 combineReducers 方法合成一个根 Reducer，这个根 Reducer 负责维护整个 State。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; combineReducers &#125; <span class="keyword">from</span> <span class="string">'redux'</span>;</span><br><span class="line"><span class="comment">// 注意这种简写形式，State 的属性名必须与子 Reducer 同名</span></span><br><span class="line"><span class="keyword">const</span> chatReducer = combineReducers(&#123;</span><br><span class="line">  Reducer1,</span><br><span class="line">  Reducer2,</span><br><span class="line">  Reducer3</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>combineReducers 干了什么事儿呢？通过简单的 combineReducers 的实现，可以了解大概的原理（可以略过不看）：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> combineReducers = <span class="function"><span class="params">reducers</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="function">(<span class="params">state = &#123;&#125;, action</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">Object</span>.keys(reducers).reduce(</span><br><span class="line">      (nextState, key) =&gt; &#123;</span><br><span class="line">        nextState[key] = reducers[key](state[key], action);</span><br><span class="line">        <span class="keyword">return</span> nextState;</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;&#125; </span><br><span class="line">    );</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<h2 id="流程"><a href="#流程" class="headerlink" title="流程"></a>流程</h2><p><img src="/../../image/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/bio4rizpf5%20(1).jpeg" alt="bio4rizpf5 (1)"></p>
<p>再回顾一下刚才的流程图，尝试走一遍 Redux 流程：</p>
<p>1、用户通过 View 发出 Action：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">store.dispatch(action);</span><br></pre></td></tr></table></figure>

<p>2、然后 Store 自动调用 Reducer，并且传入两个参数：当前 State 和收到的 Action。Reducer 会返回新的 State 。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> nextState = xxxReducer(previousState, action);</span><br></pre></td></tr></table></figure>

<p>3、State 一旦有变化，Store 就会调用监听函数。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">store.subscribe(listener);</span><br></pre></td></tr></table></figure>

<p>4、listener可以通过 store.getState() 得到当前状态。如果使用的是 React，这时可以触发重新渲染 View。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">listerner</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">let</span> newState = store.getState();</span><br><span class="line">  component.setState(newState);   </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="对比-Flux"><a href="#对比-Flux" class="headerlink" title="对比 Flux"></a><strong>对比 Flux</strong></h2><p>和 Flux 比较一下：Flux 中 Store 是各自为战的，每个 Store 只对对应的 View 负责，每次更新都只通知对应的View：</p>
<p><img src="/../../image/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/71dp1uvs0t.jpeg" alt="71dp1uvs0t"></p>
<p>Redux 中各子 Reducer 都是由根 Reducer 统一管理的，每个子 Reducer 的变化都要经过根 Reducer 的整合：</p>
<p><img src="/../../image/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/517d1ncf2c.jpeg" alt="517d1ncf2c"></p>
<p>简单来说，Redux有三大原则：<strong>单一数据源</strong>：Flux 的数据源可以是多个。<strong>State 是只读的</strong>：Flux 的 State 可以随便改。 <strong>使用纯函数来执行修改</strong>：Flux 执行修改的不一定是纯函数。</p>
<p>Redux 和 Flux 一样都是单向数据流。</p>
<h2 id="中间件"><a href="#中间件" class="headerlink" title="中间件"></a><a href="https://cloud.tencent.com/product/tdmq?from=10680" target="_blank" rel="noopener"><strong>中间件</strong></a></h2><p>刚才说到的都是比较理想的同步状态。在实际项目中，一般都会有同步和异步操作，所以 Flux、Redux 之类的思想，最终都要落地到同步异步的处理中来。</p>
<p><strong>在 Redux 中，同步的表现就是：Action 发出以后，Reducer 立即算出 State。那么异步的表现就是：Action 发出以后，过一段时间再执行 Reducer。</strong></p>
<p>那<strong>怎么才能 Reducer 在异步操作结束后自动执行呢？Redux 引入了中间件 Middleware 的概念。</strong></p>
<p>其实我们重新回顾一下刚才的流程，可以发现每一个步骤都很纯粹，都不太适合加入异步的操作，比如 Reducer，纯函数，肯定不能承担异步操作，那样会被外部IO干扰。Action呢，就是一个纯对象，放不了操作。那想来想去，只<strong>能在 View 里发送 Action 的时候，加上一些异步操作了</strong>。比如下面的代码，给原来的 dispatch 方法包裹了一层，加上了一些日志打印的功能：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> next = store.dispatch;</span><br><span class="line">store.dispatch = <span class="function"><span class="keyword">function</span> <span class="title">dispatchAndLog</span>(<span class="params">action</span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">'dispatching'</span>, action);</span><br><span class="line">  next(action);</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">'next state'</span>, store.getState());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>既然能加日志打印，当然也能加入异步操作。所以<strong>中间件简单来说，就是对 store.dispatch 方法进行一些改造的函数。</strong>不展开说了，所以如果想详细了解中间件，可以点这里。</p>
<p>Redux 提供了一个 applyMiddleware 方法来应用中间件：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> store = createStore(</span><br><span class="line">  reducer,</span><br><span class="line">  applyMiddleware(thunk, promise, logger)</span><br><span class="line">);</span><br></pre></td></tr></table></figure>

<p>这个方法主要就是把所有的中间件组成一个数组，依次执行。也就是说，任何被发送到 store 的 action 现在都会经过thunk，promise，logger 这几个中间件了。</p>
<h3 id="处理异步"><a href="#处理异步" class="headerlink" title="处理异步"></a><strong>处理异步</strong></h3><p>对于异步操作来说，有两个非常关键的时刻：发起请求的时刻，和接收到响应的时刻（可能成功，也可能失败或者超时），这两个时刻都可能会更改应用的 state。一般是这样一个过程：</p>
<p>请求开始时，dispatch 一个请求开始 Action，触发 State 更新为“正在请求”状态，View 重新渲染，比如展现个Loading啥的。 请求结束后，如果成功，dispatch 一个请求成功 Action，隐藏掉 Loading，把新的数据更新到 State；如果失败，dispatch 一个请求失败 Action，隐藏掉 Loading，给个失败提示。 显然，用 Redux 处理异步，可以自己写中间件来处理，当然大多数人会选择一些现成的支持异步处理的中间件。比如 redux-thunk 或 redux-promise 。</p>
<h3 id="Redux-thunk"><a href="#Redux-thunk" class="headerlink" title="Redux-thunk"></a><strong>Redux-thunk</strong></h3><p>thunk 比较简单，没有做太多的封装，把大部分自主权交给了用户：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> createFetchDataAction = <span class="function"><span class="keyword">function</span>(<span class="params">id</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params">dispatch, getState</span>) </span>&#123;</span><br><span class="line">        <span class="comment">// 开始请求，dispatch 一个 FETCH_DATA_START action</span></span><br><span class="line">        dispatch(&#123;</span><br><span class="line">            type: FETCH_DATA_START, </span><br><span class="line">            payload: id</span><br><span class="line">        &#125;)</span><br><span class="line">        api.fetchData(id) </span><br><span class="line">            .then(<span class="function"><span class="params">response</span> =&gt;</span> &#123;</span><br><span class="line">                <span class="comment">// 请求成功，dispatch 一个 FETCH_DATA_SUCCESS action</span></span><br><span class="line">                dispatch(&#123;</span><br><span class="line">                    type: FETCH_DATA_SUCCESS,</span><br><span class="line">                    payload: response</span><br><span class="line">                &#125;)</span><br><span class="line">            &#125;)</span><br><span class="line">            .catch(<span class="function"><span class="params">error</span> =&gt;</span> &#123;</span><br><span class="line">                <span class="comment">// 请求失败，dispatch 一个 FETCH_DATA_FAILED action   </span></span><br><span class="line">                dispatch(&#123;</span><br><span class="line">                    type: FETCH_DATA_FAILED,</span><br><span class="line">                    payload: error</span><br><span class="line">                &#125;)</span><br><span class="line">            &#125;) </span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//reducer</span></span><br><span class="line"><span class="keyword">const</span> reducer = <span class="function"><span class="keyword">function</span>(<span class="params">oldState, action</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">switch</span>(action.type) &#123;</span><br><span class="line">    <span class="keyword">case</span> FETCH_DATA_START : </span><br><span class="line">        <span class="comment">// 处理 loading 等</span></span><br><span class="line">    <span class="keyword">case</span> FETCH_DATA_SUCCESS : </span><br><span class="line">        <span class="comment">// 更新 store 等</span></span><br><span class="line">    <span class="keyword">case</span> FETCH_DATA_FAILED : </span><br><span class="line">        <span class="comment">// 提示异常</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>缺点就是用户要写的代码有点多，可以看到上面的代码比较啰嗦，一个请求就要搞这么一套东西。</p>
<h3 id="Redux-promise"><a href="#Redux-promise" class="headerlink" title="Redux-promise"></a><strong>Redux-promise</strong></h3><p>redus-promise 和 redux-thunk 的思想类似，只不过做了一些简化，成功失败手动 dispatch 被封装成自动了：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> FETCH_DATA = <span class="string">'FETCH_DATA'</span></span><br><span class="line"><span class="comment">//action creator</span></span><br><span class="line"><span class="keyword">const</span> getData = <span class="function"><span class="keyword">function</span>(<span class="params">id</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">        type: FETCH_DATA,</span><br><span class="line">        payload: api.fetchData(id) <span class="comment">// 直接将 promise 作为 payload</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//reducer</span></span><br><span class="line"><span class="keyword">const</span> reducer = <span class="function"><span class="keyword">function</span>(<span class="params">oldState, action</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">switch</span>(action.type) &#123;</span><br><span class="line">    <span class="keyword">case</span> FETCH_DATA: </span><br><span class="line">        <span class="keyword">if</span> (action.status === <span class="string">'success'</span>) &#123;</span><br><span class="line">             <span class="comment">// 更新 store 等处理</span></span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// 提示异常</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>刚才的什么 then、catch 之类的被中间件自行处理了，代码简单不少，不过要处理 Loading 啥的，还需要写额外的代码。</p>
<p><strong>其实任何时候都是这样：封装少，自由度高，但是代码就会变复杂；封装多，代码变简单了，但是自由度就会变差。redux-thunk 和 redux-promise 刚好就是代表这两个面。</strong></p>
<p>redux-thunk 和 redux-promise 的具体使用就不介绍了，这里只聊一下大概的思路。大部分简单的异步业务场景，redux-thunk 或者 redux-promise 都可以满足了。</p>
<p>上面说的 Flux 和 Redux，和具体的前端框架没有什么关系，只是思想和约定层面。下面就要和我们常用的 Vue 或 React 结合起来了：</p>
<h1 id="Vuex"><a href="#Vuex" class="headerlink" title="Vuex"></a><strong>Vuex</strong></h1><p>Vuex 主要用于 Vue，和 Flux，Redux 的思想很类似。</p>
<p><img src="/../../image/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/7e4tfmk7of.jpeg" alt="7e4tfmk7of"></p>
<h2 id="Store"><a href="#Store" class="headerlink" title="Store"></a><strong>Store</strong></h2><p>每一个 Vuex 里面有<strong>一个全局的 Store</strong>，包含着应用中的状态 State，这个 State 只是需要在组件中共享的数据，不用放所有的 State，没必要。这个 State 是单一的，和 Redux 类似，所以，<strong>一个应用仅会包含一个 Store 实例。</strong>单一状态树的好处是能够直接地定位任一特定的状态片段，在调试的过程中也能轻易地取得整个当前应用状态的快照。</p>
<p>Vuex通过 store 选项，把 state 注入到了整个应用中，这样子组件能通过 this.$store 访问到 state 了。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> Vue(&#123;</span><br><span class="line">  el: <span class="string">'#app'</span>,</span><br><span class="line">  <span class="comment">// 把 store 对象提供给 “store” 选项，这可以把 store 的实例注入所有的子组件</span></span><br><span class="line">  store,</span><br><span class="line">  components: &#123; Counter &#125;,</span><br><span class="line">  template: <span class="string">`</span></span><br><span class="line"><span class="string">    &lt;div class="app"&gt;</span></span><br><span class="line"><span class="string">      &lt;counter&gt;&lt;/counter&gt;</span></span><br><span class="line"><span class="string">    &lt;/div&gt;</span></span><br><span class="line"><span class="string">  `</span></span><br><span class="line">&#125;)</span><br><span class="line"><span class="keyword">const</span> Counter = &#123;</span><br><span class="line">  template: <span class="string">`&lt;div&gt;&#123;&#123; count &#125;&#125;&lt;/div&gt;`</span>,</span><br><span class="line">  computed: &#123;</span><br><span class="line">    count () &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">this</span>.$store.state.count</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>State 改变，View 就会跟着改变，这个改变利用的是 Vue 的响应式机制。</p>
<h2 id="Mutation"><a href="#Mutation" class="headerlink" title="Mutation"></a><strong>Mutation</strong></h2><p>显而易见，State 不能直接改，需要通过一个约定的方式，<strong>这个方式在 Vuex 里面叫做 mutation，更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。</strong>Vuex 中的 mutation 非常类似于事件：每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> store = <span class="keyword">new</span> Vuex.Store(&#123;</span><br><span class="line">  state: &#123;</span><br><span class="line">    count: <span class="number">1</span></span><br><span class="line">  &#125;,</span><br><span class="line">  mutations: &#123;</span><br><span class="line">    increment (state) &#123;</span><br><span class="line">      <span class="comment">// 变更状态</span></span><br><span class="line">      state.count++</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>触发 mutation 事件的方式不是直接调用，比如 increment(state) 是不行的，<strong>而要通过 store.commit 方法</strong>：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">store.commit(<span class="string">'increment'</span>)</span><br></pre></td></tr></table></figure>

<p>注意：<strong>mutation 都是同步事务</strong>。</p>
<p>mutation 有些类似 Redux 的 Reducer，但是 Vuex 不要求每次都搞一个新的 State，可以直接修改 State，这块儿又和 Flux 有些类似。具尤大的说法，Redux 强制的 immutability，在保证了每一次状态变化都能追踪的情况下强制的 immutability 带来的收益很有限，为了同构而设计的 API 很繁琐，必须依赖第三方库才能相对高效率地获得状态树的局部状态，这些都是 Redux 不足的地方，所以也被 Vuex 舍掉了。</p>
<p>到这里，其实可以感觉到 Flux、Redux、Vuex 三个的思想都差不多，在具体细节上有一些差异，<strong>总的来说都是让 View 通过某种方式触发 Store 的事件或方法，Store 的事件或方法对 State 进行修改或返回一个新的 State，State 改变之后，View 发生响应式改变。</strong></p>
<h2 id="Action-1"><a href="#Action-1" class="headerlink" title="Action"></a><strong>Action</strong></h2><p>到这里又该处理异步这块儿了。mutation 是必须同步的，这个很好理解，<strong>和之前的 reducer 类似，不同步修改的话，会很难调试，不知道改变什么时候发生，也很难确定先后顺序</strong>，A、B两个 mutation，调用顺序可能是 A -&gt; B，但是最终改变 State 的结果可能是 B -&gt; A。</p>
<p><strong>对比Redux的中间件，Vuex 加入了 Action 这个东西来处理异步，Vuex的想法是把同步和异步拆分开，异步操作想咋搞咋搞，但是不要干扰了同步操作。</strong>View 通过 store.dispatch(‘increment’) 来触发某个 Action，Action 里面不管执行多少异步操作，<strong>完事之后都通过 store.commit(‘increment’) 来触发 mutation</strong>，一个 Action 里面可以触发多个 mutation。所以 Vuex 的Action 类似于一个灵活好用的中间件。</p>
<p>Vuex 把同步和异步操作通过 mutation 和 Action 来分开处理，是一种方式。但不代表是唯一的方式，还有很多方式，比如就不用 Action，而是在应用内部调用异步请求，请求完毕直接 commit mutation，当然也可以。Vuex 还引入了 Getter，这个可有可无，只不过是方便计算属性的复用。</p>
<p><strong>Vuex 单一状态树并不影响模块化，把 State 拆了，最后组合在一起就行。Vuex 引入了 Module 的概念，每个 Module 有自己的 state、mutation、action、getter，其实就是把一个大的 Store 拆开。</strong>总的来看，Vuex 的方式比较清晰，适合 Vue 的思想，在实际开发中也比较方便。</p>
<h2 id="对比Redux"><a href="#对比Redux" class="headerlink" title="对比Redux"></a><strong>对比Redux</strong></h2><p>Redux：view——&gt;actions——&gt;reducer——&gt;state变化——&gt;view变化（同步异步一样）</p>
<p>Vuex：view——&gt;commit——&gt;mutations——&gt;state变化——&gt;view变化（同步操作） </p>
<p>view——&gt;dispatch——&gt;actions——&gt;mutations——&gt;state变化——&gt;view变化（异步操作）</p>
<h1 id="React-redux"><a href="#React-redux" class="headerlink" title="React-redux"></a><strong>React-redux</strong></h1><p>Redux 和 Flux 类似，只是一种思想或者规范，它和 React 之间没有关系。Redux 支持 React、Angular、Ember、jQuery 甚至纯 JavaScript。</p>
<p>但是因为 React 包含函数式的思想，也是单向数据流，和 Redux 很搭，所以一般都用 Redux 来进行状态管理。为了简单处理 Redux 和 React UI 的绑定，一般通过一个叫 react-redux 的库和 React 配合使用，这个是 react 官方出的（如果不用 react-redux，那么手动处理 Redux 和 UI 的绑定，需要写很多重复的代码，很容易出错，而且有很多 UI 渲染逻辑的优化不一定能处理好）。</p>
<p><strong>Redux将React组件分为容器型组件和展示型组件</strong>，容器型组件一般通过connect函数生成，它订阅了全局状态的变化，通过mapStateToProps函数，可以对全局状态进行过滤，而展示型组件不直接从global state获取数据，其数据来源于父组件。</p>
<p><img src="/../../image/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/7mhhp1q702.jpeg" alt="7mhhp1q702"></p>
<p><strong>如果一个组件既需要UI呈现，又需要业务逻辑处理，那就得拆，拆成一个容器组件包着一个展示组件。</strong></p>
<p>因为 react-redux 只是 redux 和 react 结合的一种实现，除了刚才说的组件拆分，并没有什么新奇的东西，所以只拿一个简单TODO项目的部分代码来举例：入口文件 index.js，把 redux 的相关 store、reducer 通过 Provider 注册到 App 里面，这样子组件就可以拿到 store 了。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React <span class="keyword">from</span> <span class="string">'react'</span></span><br><span class="line"><span class="keyword">import</span> &#123; render &#125; <span class="keyword">from</span> <span class="string">'react-dom'</span></span><br><span class="line"><span class="keyword">import</span> &#123; Provider &#125; <span class="keyword">from</span> <span class="string">'react-redux'</span></span><br><span class="line"><span class="keyword">import</span> &#123; createStore &#125; <span class="keyword">from</span> <span class="string">'redux'</span></span><br><span class="line"><span class="keyword">import</span> rootReducer <span class="keyword">from</span> <span class="string">'./reducers'</span></span><br><span class="line"><span class="keyword">import</span> App <span class="keyword">from</span> <span class="string">'./components/App'</span></span><br><span class="line"><span class="keyword">const</span> store = createStore(rootReducer)</span><br><span class="line">render(</span><br><span class="line">  &lt;Provider store=&#123;store&#125;&gt;</span><br><span class="line">    &lt;App /&gt;</span><br><span class="line">  &lt;<span class="regexp">/Provider&gt;,</span></span><br><span class="line"><span class="regexp">  document.getElementById('root')</span></span><br><span class="line"><span class="regexp">)</span></span><br></pre></td></tr></table></figure>

<p>actions/index.js，创建 Action：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> nextTodoId = <span class="number">0</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> addTodo = <span class="function"><span class="params">text</span> =&gt;</span> (&#123;</span><br><span class="line">  type: <span class="string">'ADD_TODO'</span>,</span><br><span class="line">  id: nextTodoId++,</span><br><span class="line">  text&#125;)</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> setVisibilityFilter = <span class="function"><span class="params">filter</span> =&gt;</span> (&#123;</span><br><span class="line">  type: <span class="string">'SET_VISIBILITY_FILTER'</span>,</span><br><span class="line">  filter&#125;)</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> toggleTodo = <span class="function"><span class="params">id</span> =&gt;</span> (&#123;</span><br><span class="line">  type: <span class="string">'TOGGLE_TODO'</span>,</span><br><span class="line">  id&#125;)</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> VisibilityFilters = &#123;</span><br><span class="line">  SHOW_ALL: <span class="string">'SHOW_ALL'</span>,</span><br><span class="line">  SHOW_COMPLETED: <span class="string">'SHOW_COMPLETED'</span>,</span><br><span class="line">  SHOW_ACTIVE: <span class="string">'SHOW_ACTIVE'</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>reducers/todos.js，创建 Reducers：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> todos = <span class="function">(<span class="params">state = [], action</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">switch</span> (action.type) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">'ADD_TODO'</span>:</span><br><span class="line">      <span class="keyword">return</span> [</span><br><span class="line">        ...state,</span><br><span class="line">        &#123;</span><br><span class="line">          id: action.id,</span><br><span class="line">          text: action.text,</span><br><span class="line">          completed: <span class="literal">false</span></span><br><span class="line">        &#125;</span><br><span class="line">      ]</span><br><span class="line">    <span class="keyword">case</span> <span class="string">'TOGGLE_TODO'</span>:</span><br><span class="line">      <span class="keyword">return</span> state.map(<span class="function"><span class="params">todo</span> =&gt;</span></span><br><span class="line">        todo.id === action.id ? &#123; ...todo, <span class="attr">completed</span>: !todo.completed &#125; : todo</span><br><span class="line">      )</span><br><span class="line">    <span class="keyword">default</span>:</span><br><span class="line">      <span class="keyword">return</span> state</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> todos</span><br></pre></td></tr></table></figure>

<p>reducers/index.js，把所有的 Reducers 绑定到一起：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; combineReducers &#125; <span class="keyword">from</span> <span class="string">'redux'</span></span><br><span class="line"><span class="keyword">import</span> todos <span class="keyword">from</span> <span class="string">'./todos'</span></span><br><span class="line"><span class="keyword">import</span> visibilityFilter <span class="keyword">from</span> <span class="string">'./visibilityFilter'</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> combineReducers(&#123;</span><br><span class="line">  todos,</span><br><span class="line">  visibilityFilter,</span><br><span class="line">  ...</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>containers/VisibleTodoList.js，容器组件，connect 负责连接React组件和Redux Store：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; connect &#125; <span class="keyword">from</span> <span class="string">'react-redux'</span></span><br><span class="line"><span class="keyword">import</span> &#123; toggleTodo &#125; <span class="keyword">from</span> <span class="string">'../actions'</span></span><br><span class="line"><span class="keyword">import</span> TodoList <span class="keyword">from</span> <span class="string">'../components/TodoList'</span></span><br><span class="line"><span class="keyword">const</span> getVisibleTodos = <span class="function">(<span class="params">todos, filter</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">switch</span> (filter) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">'SHOW_COMPLETED'</span>:</span><br><span class="line">      <span class="keyword">return</span> todos.filter(<span class="function"><span class="params">t</span> =&gt;</span> t.completed)</span><br><span class="line">    <span class="keyword">case</span> <span class="string">'SHOW_ACTIVE'</span>:</span><br><span class="line">      <span class="keyword">return</span> todos.filter(<span class="function"><span class="params">t</span> =&gt;</span> !t.completed)</span><br><span class="line">    <span class="keyword">case</span> <span class="string">'SHOW_ALL'</span>:</span><br><span class="line">    <span class="keyword">default</span>:</span><br><span class="line">      <span class="keyword">return</span> todos</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// mapStateToProps 函数指定如何把当前 Redux store state 映射到展示组件的 props 中</span></span><br><span class="line"><span class="keyword">const</span> mapStateToProps = <span class="function"><span class="params">state</span> =&gt;</span> (&#123;</span><br><span class="line">  todos: getVisibleTodos(state.todos, state.visibilityFilter)</span><br><span class="line">&#125;)</span><br><span class="line"><span class="comment">// mapDispatchToProps 方法接收 dispatch() 方法并返回期望注入到展示组件的 props 中的回调方法。</span></span><br><span class="line"><span class="keyword">const</span> mapDispatchToProps = <span class="function"><span class="params">dispatch</span> =&gt;</span> (&#123;</span><br><span class="line">  toggleTodo: <span class="function"><span class="params">id</span> =&gt;</span> dispatch(toggleTodo(id))</span><br><span class="line">&#125;)</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> connect(</span><br><span class="line">  mapStateToProps,</span><br><span class="line">  mapDispatchToProps)(TodoList)</span><br></pre></td></tr></table></figure>

<p><strong>简单来说，react-redux 就是多了个 connect 方法连接容器组件和UI组件，这里的“连接”就是一种映射：mapStateToProps 把容器组件的 state 映射到UI组件的 props mapDispatchToProps 把UI组件的事件映射到 dispatch 方法</strong></p>
<h1 id="Redux-saga"><a href="#Redux-saga" class="headerlink" title="Redux-saga"></a><strong>Redux-saga</strong></h1><p>刚才介绍了两个Redux 处理异步的中间件 redux-thunk 和 redux-promise，当然 redux 的异步中间件还有很多，他们可以处理大部分场景，这些中间件的思想基本上都是把异步请求部分放在了 action creator 中，理解起来比较简单。</p>
<p><strong>redux-saga 采用了另外一种思路，它没有把异步操作放在 action creator 中，也没有去处理 reductor，而是把所有的异步操作看成“线程”，可以通过普通的action去触发它，当操作完成时也会触发action作为输出。saga 的意思本来就是一连串的事件。</strong></p>
<p><strong>redux-saga 把异步获取数据这类的操作都叫做副作用（Side Effect）</strong>，它的目标就是把这些副作用管理好，让他们执行更高效，测试更简单，在处理故障时更容易。</p>
<p>在聊 redux-saga 之前，需要熟悉一些预备知识，那就是 ES6 的 Generator。</p>
<p>如果从没接触过 Generator 的话，看着下面的代码，给你个1分钟傻瓜式速成，函数加个星号就是 Generator 函数了，Generator 就是个骂街生成器，Generator 函数里可以写一堆 yield 关键字，可以记成“丫的”，Generator 函数执行的时候，啥都不干，就等着调用 next 方法，按照顺序把标记为“丫的”的地方一个一个拎出来骂（遍历执行），骂到最后没有“丫的”标记了，就返回最后的return值，然后标记为 done: true，也就是骂完了（上面只是帮助初学者记忆，别喷~）。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span>* <span class="title">helloWorldGenerator</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">yield</span> <span class="string">'hello'</span>;</span><br><span class="line">  <span class="keyword">yield</span> <span class="string">'world'</span>;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">'ending'</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> hw = helloWorldGenerator();</span><br><span class="line">hw.next() <span class="comment">// 先把 'hello' 拎出来，done: false 代表还没骂完</span></span><br><span class="line"><span class="comment">// &#123; value: 'hello', done: false &#125; next() 方法有固定的格式，value 是返回值，done 代表是否遍历结束</span></span><br><span class="line">hw.next() <span class="comment">// 再把 'world' 拎出来，done: false 代表还没骂完</span></span><br><span class="line"><span class="comment">// &#123; value: 'world', done: false &#125;</span></span><br><span class="line">hw.next() <span class="comment">// 没有 yield 了，就把最后的 return 'ending' 拎出来，done: true 代表骂完了</span></span><br><span class="line"><span class="comment">// &#123; value: 'ending', done: true &#125;</span></span><br><span class="line">hw.next() <span class="comment">// 没有 yield，也没有 return 了，真的骂完了，只能挤出来一个 undefined 了，done: true 代表骂完了</span></span><br><span class="line"><span class="comment">// &#123; value: undefined, done: true &#125;</span></span><br></pre></td></tr></table></figure>

<p>这样搞有啥好处呢？<strong>我们发现 Generator 函数的很多代码可以被延缓执行，也就是具备了暂停和记忆的功能</strong>：遇到yield表达式，就暂停执行后面的操作，并将紧跟在yield后面的那个表达式的值，作为返回的对象的value属性值，等着下一次调用next方法时，再继续往下执行。用 Generator 来写异步代码，大概长这样：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span>* <span class="title">gen</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">  <span class="keyword">var</span> url = <span class="string">'https://api.github.com/users/github'</span>;</span><br><span class="line">  <span class="keyword">var</span> jsonData = <span class="keyword">yield</span> fetch(url);</span><br><span class="line">  <span class="built_in">console</span>.log(jsonData);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> g = gen();</span><br><span class="line"><span class="keyword">var</span> result = g.next(); </span><br><span class="line"><span class="comment">// 这里的result是 &#123; value: fetch('https://api.github.com/users/github'), done: true &#125;</span></span><br><span class="line"><span class="comment">// fetch(url) 是一个 Promise，所以需要 then 来执行下一步</span></span><br><span class="line">result.value.then(<span class="function"><span class="keyword">function</span>(<span class="params">data</span>)</span>&#123;</span><br><span class="line">  <span class="keyword">return</span> data.json();</span><br><span class="line">&#125;).then(<span class="function"><span class="keyword">function</span>(<span class="params">data</span>)</span>&#123;</span><br><span class="line">  <span class="comment">// 获取到 json data，然后作为参数调用 next，相当于把 data 传给了 jsonData，然后执行 console.log(jsonData);</span></span><br><span class="line">  g.next(data);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p><strong>再回到 redux-saga 来，可以把 saga 想象成开了一个以最快速度不断地调用 next 方法并尝试获取所有 yield 表达式值的线程。举个例子：</strong></p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// saga.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; take, put &#125; <span class="keyword">from</span> <span class="string">'redux-saga/effects'</span></span><br><span class="line"><span class="function"><span class="keyword">function</span>* <span class="title">mySaga</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="comment">// 阻塞: take方法就是等待 USER_INTERACTED_WITH_UI_ACTION 这个 action 执行</span></span><br><span class="line">    <span class="keyword">yield</span> take(USER_INTERACTED_WITH_UI_ACTION);</span><br><span class="line">    <span class="comment">// 阻塞: put方法将同步发起一个 action</span></span><br><span class="line">    <span class="keyword">yield</span> put(SHOW_LOADING_ACTION, &#123;<span class="attr">isLoading</span>: <span class="literal">true</span>&#125;);</span><br><span class="line">    <span class="comment">// 阻塞: 将等待 FetchFn 结束，等待返回的 Promise</span></span><br><span class="line">    <span class="keyword">const</span> data = <span class="keyword">yield</span> call(FetchFn, <span class="string">'https://my.server.com/getdata'</span>);</span><br><span class="line">    <span class="comment">// 阻塞: 将同步发起 action (使用刚才返回的 Promise.then)</span></span><br><span class="line">    <span class="keyword">yield</span> put(SHOW_DATA_ACTION, &#123;<span class="attr">data</span>: data&#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这里用了好几个yield，简单理解，也就是每个 yield 都发起了阻塞，saga 会等待执行结果返回，再执行下一指令。也就是相当于take、put、call、put 这几个方法的调用变成了同步的，上面的全部完成返回了，才会执行下面的，类似于 await。</p>
<p><strong>用了 saga，我们就可以很细粒度的控制各个副作用每一步的操作，可以把异步操作和同步发起 action 一起，随便的排列组合。</strong>saga 还提供 takeEvery、takeLatest 之类的辅助函数，来控制是否允许多个异步请求同时执行，尤其是 takeLatest，方便处理由于网络延迟造成的多次请求数据冲突或混乱的问题。</p>
<p>saga 看起来很复杂，主要原因可能是因为大家不熟悉 Generator 的语法，还有需要学习一堆新增的 API 。如果抛开这些记忆的东西，改造一下，再来看一下代码：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">mySaga</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">if</span> (action.type === <span class="string">'USER_INTERACTED_WITH_UI_ACTION'</span>) &#123;</span><br><span class="line">        store.dispatch(&#123; <span class="attr">type</span>: <span class="string">'SHOW_LOADING_ACTION'</span>, <span class="attr">isLoading</span>: <span class="literal">true</span>&#125;);</span><br><span class="line">        <span class="keyword">const</span> data = <span class="keyword">await</span> Fetch(<span class="string">'https://my.server.com/getdata'</span>);</span><br><span class="line">        store.dispatch(&#123; <span class="attr">type</span>: <span class="string">'SHOW_DATA_ACTION'</span>, <span class="attr">data</span>: data&#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上面的代码就很清晰了吧，全部都是同步的写法，无比顺畅，当然直接这样写是不支持的，所以那些 Generator 语法和API，无非就是做一些适配而已。</p>
<p>saga 还能很方便的并行执行异步任务，或者让两个异步任务竞争：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 并行执行，并等待所有的结果，类似 Promise.all 的行为</span></span><br><span class="line"><span class="keyword">const</span> [users, repos] = <span class="keyword">yield</span> [</span><br><span class="line">  call(fetch, <span class="string">'/users'</span>),</span><br><span class="line">  call(fetch, <span class="string">'/repos'</span>)</span><br><span class="line">]</span><br><span class="line"><span class="comment">// 并行执行，哪个先完成返回哪个，剩下的就取消掉了</span></span><br><span class="line"><span class="keyword">const</span> &#123;posts, timeout&#125; = <span class="keyword">yield</span> race(&#123;</span><br><span class="line">  posts: call(fetchApi, <span class="string">'/posts'</span>),</span><br><span class="line">  timeout: call(delay, <span class="number">1000</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>saga 的每一步都可以做一些断言（assert）之类的，所以非常方便测试。而且很容易测试到不同的分支。</p>
<p>这里不讨论更多 saga 的细节，大家了解 saga 的思想就行，细节请看文档。</p>
<h2 id="对比-Redux-thunk"><a href="#对比-Redux-thunk" class="headerlink" title="对比 Redux-thunk"></a><strong>对比 Redux-thunk</strong></h2><p><img src="/../../image/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/2az1mka48t.jpeg" alt="2az1mka48t"></p>
<p>比较一下 redux-thunk 和 redux-saga 的代码：</p>
<p><img src="/../../image/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/w3otvvj866.jpeg" alt="w3otvvj866"></p>
<p>和 redux-thunk 等其他异步中间件对比来说，redux-saga 主要有下面几个特点：异步数据获取的相关业务逻辑放在了单独的 saga.js 中，不再是掺杂在 action.js 或 component.js 中。dispatch 的参数是标准的 action，没有魔法。saga 代码采用类似同步的方式书写，代码变得更易读。代码异常/请求失败 都可以直接通过 try/catch 语法直接捕获处理。* 很容易测试，如果是 thunk 的 Promise，测试的话就需要不停的 mock 不同的数据。</p>
<p><strong>其实 redux-saga 是用一些学习的复杂度，换来了代码的高可维护性，还是很值得在项目中使用的。</strong></p>
<h1 id="Dva"><a href="#Dva" class="headerlink" title="Dva"></a><strong>Dva</strong></h1><p>Dva是什么呢？官方的定义是：<strong>dva 首先是一个基于 redux 和 redux-saga 的数据流方案，然后为了简化开发体验，dva 还额外内置了 react-router 和 fetch，所以也可以理解为一个轻量级的应用框架。</strong></p>
<p>简单理解，就是让<strong>使用 react-redux 和 redux-saga 编写的代码组织起来更合理</strong>，维护起来更方便。</p>
<p>之前我们聊了 redux、react-redux、redux-saga 之类的概念，大家肯定觉得头昏脑涨的，什么 action、reducer、saga 之类的，写一个功能要在这些js文件里面不停的切换。</p>
<p>dva 做的事情很简单，就是让这些东西可以写到一起，不用分开来写了。比如：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">app.model(&#123;</span><br><span class="line">  <span class="comment">// namespace - 对应 reducer 在 combine 到 rootReducer 时的 key 值</span></span><br><span class="line">  namespace: <span class="string">'products'</span>,</span><br><span class="line">  <span class="comment">// state - 对应 reducer 的 initialState</span></span><br><span class="line">  state: &#123;</span><br><span class="line">    list: [],</span><br><span class="line">    loading: <span class="literal">false</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="comment">// subscription - 在 dom ready 后执行</span></span><br><span class="line">  subscriptions: [</span><br><span class="line">    <span class="function"><span class="keyword">function</span>(<span class="params">dispatch</span>) </span>&#123;</span><br><span class="line">      dispatch(&#123;<span class="attr">type</span>: <span class="string">'products/query'</span>&#125;);</span><br><span class="line">    &#125;,</span><br><span class="line">  ],</span><br><span class="line">  <span class="comment">// effects - 对应 saga，并简化了使用</span></span><br><span class="line">  effects: &#123;</span><br><span class="line">    [<span class="string">'products/query'</span>]: <span class="function"><span class="keyword">function</span>*(<span class="params"></span>) </span>&#123;</span><br><span class="line">      <span class="keyword">yield</span> call(delay(<span class="number">800</span>));</span><br><span class="line">      <span class="keyword">yield</span> put(&#123;</span><br><span class="line">        type: <span class="string">'products/query/success'</span>,</span><br><span class="line">        payload: [<span class="string">'ant-tool'</span>, <span class="string">'roof'</span>],</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="comment">// reducers - 就是传统的 reducers</span></span><br><span class="line">  reducers: &#123;</span><br><span class="line">    [<span class="string">'products/query'</span>](state) &#123;</span><br><span class="line">      <span class="keyword">return</span> &#123; ...state, <span class="attr">loading</span>: <span class="literal">true</span>, &#125;;</span><br><span class="line">    &#125;,</span><br><span class="line">    [<span class="string">'products/query/success'</span>](state, &#123; payload &#125;) &#123;</span><br><span class="line">      <span class="keyword">return</span> &#123; ...state, <span class="attr">loading</span>: <span class="literal">false</span>, <span class="attr">list</span>: payload &#125;;</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>以前书写的方式是创建 sagas/products.js, reducers/products.js 和 actions/products.js，然后把 saga、action、reducer 啥的分开来写，来回切换，现在写在一起就方便多了。</p>
<p>比如传统的 TODO 应用，用 redux + redux-saga 来表示结构，就是这样：</p>
<p><img src="/../../image/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/q5b4n6r4nt.jpeg" alt="q5b4n6r4nt"></p>
<p>saga 拦截 add 这个 action, 发起 http 请求, 如果请求成功, 则继续向 reducer 发一个 addTodoSuccess 的 action, 提示创建成功, 反之则发送 addTodoFail 的 action 即可。</p>
<p>如果使用 Dva，那么结构图如下：</p>
<p><img src="/../../image/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/pj9kabuq19.jpeg" alt="pj9kabuq19"></p>
<p>整个结构变化不大，最主要的就是把 store 及 saga 统一为一个 model 的概念（有点类似 Vuex 的 Module），写在了一个 js 文件里。增加了一个 Subscriptions, 用于收集其他来源的 action，比如快捷键操作。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">app.model(&#123;</span><br><span class="line">  namespace: <span class="string">'count'</span>,</span><br><span class="line">  state: &#123;</span><br><span class="line">    record: <span class="number">0</span>,</span><br><span class="line">    current: <span class="number">0</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  reducers: &#123;</span><br><span class="line">    add(state) &#123;</span><br><span class="line">      <span class="keyword">const</span> newCurrent = state.current + <span class="number">1</span>;</span><br><span class="line">      <span class="keyword">return</span> &#123; ...state,</span><br><span class="line">        record: newCurrent &gt; state.record ? newCurrent : state.record,</span><br><span class="line">        current: newCurrent,</span><br><span class="line">      &#125;;</span><br><span class="line">    &#125;,</span><br><span class="line">    minus(state) &#123;</span><br><span class="line">      <span class="keyword">return</span> &#123; ...state, <span class="attr">current</span>: state.current - <span class="number">1</span>&#125;;</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">  effects: &#123;</span><br><span class="line">    *add(action, &#123; call, put &#125;) &#123;</span><br><span class="line">      <span class="keyword">yield</span> call(delay, <span class="number">1000</span>);</span><br><span class="line">      <span class="keyword">yield</span> put(&#123; <span class="attr">type</span>: <span class="string">'minus'</span> &#125;);</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">  subscriptions: &#123;</span><br><span class="line">    keyboardWatcher(&#123; dispatch &#125;) &#123;</span><br><span class="line">      key(<span class="string">'⌘+up, ctrl+up'</span>, () =&gt; &#123; dispatch(&#123;<span class="attr">type</span>:<span class="string">'add'</span>&#125;) &#125;);</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>之前我们说过约定优于配置的思想，Dva正式借鉴了这个思想。</p>
<h1 id="MobX"><a href="#MobX" class="headerlink" title="MobX"></a><strong>MobX</strong></h1><p>前面扯了这么多，其实还都是 Flux 体系的，都是单向数据流方案。接下来要说的 MobX，就和他们不太一样了。</p>
<p>我们先清空一下大脑，回到初心，什么是初心？就是我们最初要解决的问题是什么？最初我们其实为了解决应用状态管理的问题，不管是 Redux 还是 MobX，把状态管理好是前提。什么叫把状态管理好，简单来说就是<strong>：统一维护公共的应用状态，以统一并且可控的方式更新状态，状态更新后，View跟着更新。</strong>不管是什么思想，达成这个目标就ok。</p>
<p>Flux 体系的状态管理方式，只是一个选项，但并不代表是唯一的选项。MobX 就是另一个选项。</p>
<p>MobX背后的哲学很简单<strong>：任何源自应用状态的东西都应该自动地获得。译成人话就是状态只要一变，其他用到状态的地方就都跟着自动变。</strong></p>
<p><img src="/../../image/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/hn7fil8nbb.jpeg" alt="hn7fil8nbb"></p>
<p>看这篇文章的人，大概率会对面向对象的思想比较熟悉，而对函数式编程的思想略陌生。Flux 或者说 Redux 的思想主要就是函数式编程（FP）的思想，所以学习起来会觉得累一些。而 MobX 更接近于面向对象编程，<strong>它把 state 包装成可观察的对象，这个对象会驱动各种改变</strong>。什么是可观察？<strong>就是 MobX 老大哥在看着 state 呢。state 只要一改变，所有用到它的地方就都跟着改变了。这样整个 View 可以被 state 来驱动。</strong></p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> obj = observable(&#123;</span><br><span class="line">    a: <span class="number">1</span>,</span><br><span class="line">    b: <span class="number">2</span></span><br><span class="line">&#125;)</span><br><span class="line">autoRun(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(obj.a)</span><br><span class="line">&#125;)</span><br><span class="line">obj.b = <span class="number">3</span> <span class="comment">// 什么都没有发生</span></span><br><span class="line">obj.a = <span class="number">2</span> <span class="comment">// observe 函数的回调触发了，控制台输出：2</span></span><br></pre></td></tr></table></figure>

<p>上面的obj，他的 obj.a 属性被使用了，那么只要 obj.a 属性一变，所有使用的地方都会被调用。autoRun 就是这个老大哥，他看着所有依赖 obj.a 的地方，也就是收集所有对 obj.a 的依赖。当 obj.a 改变时，老大哥就会触发所有依赖去更新。</p>
<p><strong>MobX 允许有多个 store，而且这些 store 里的 state 可以直接修改，不用像 Redux 那样每次还返回个新的。这个有点像 Vuex，自由度更高，写的代码更少。</strong>不过它也会让代码不好维护。</p>
<p>MobX 和 Flux、Redux 一样，都是和具体的前端框架无关的，也就是说可以用于 React（mobx-react) 或者 Vue（mobx-vue)。一般来说，用到 React 比较常见，很少用于 Vue，因为 Vuex 本身就类似 MobX，很灵活。如果我们把 MobX 用于 React 或者 Vue，可以看到很多 setState() 和 <a href="http://this.state.xxx/" target="_blank" rel="noopener">http://this.state.xxx</a> = 这样的处理都可以省了。</p>
<p>还是和上面一样，只介绍思想。具体 MobX 的使用，可以看这里。</p>
<h2 id="对比-Redux"><a href="#对比-Redux" class="headerlink" title="对比 Redux"></a><strong>对比 Redux</strong></h2><p>我们直观地上两坨实现计数器代码：</p>
<p><img src="/../../image/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/egue4r5wnl.gif" alt="egue4r5wnl"></p>
<p>Redux：</p>
<figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React, &#123; Component &#125; <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;</span><br><span class="line">  createStore,</span><br><span class="line">  bindActionCreators,</span><br><span class="line">&#125; <span class="keyword">from</span> <span class="string">'redux'</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; Provider, connect &#125; <span class="keyword">from</span> <span class="string">'react-redux'</span>;</span><br><span class="line"><span class="comment">// ①action types</span></span><br><span class="line"><span class="keyword">const</span> COUNTER_ADD = <span class="string">'counter_add'</span>;</span><br><span class="line"><span class="keyword">const</span> COUNTER_DEC = <span class="string">'counter_dec'</span>;</span><br><span class="line"><span class="keyword">const</span> initialState = &#123;<span class="attr">a</span>: <span class="number">0</span>&#125;;</span><br><span class="line"><span class="comment">// ②reducers</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">reducers</span>(<span class="params">state = initialState, action</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">switch</span> (action.type) &#123;</span><br><span class="line">  <span class="keyword">case</span> COUNTER_ADD:</span><br><span class="line">    <span class="keyword">return</span> &#123;...state, <span class="attr">a</span>: state.a+<span class="number">1</span>&#125;;</span><br><span class="line">  <span class="keyword">case</span> COUNTER_DEC:</span><br><span class="line">    <span class="keyword">return</span> &#123;...state, <span class="attr">a</span>: state.a<span class="number">-1</span>&#125;;</span><br><span class="line">  <span class="keyword">default</span>:</span><br><span class="line">    <span class="keyword">return</span> state</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// ③action creator</span></span><br><span class="line"><span class="keyword">const</span> incA = <span class="function"><span class="params">()</span> =&gt;</span> (&#123; <span class="attr">type</span>: COUNTER_ADD &#125;);</span><br><span class="line"><span class="keyword">const</span> decA = <span class="function"><span class="params">()</span> =&gt;</span> (&#123; <span class="attr">type</span>: COUNTER_DEC &#125;);</span><br><span class="line"><span class="keyword">const</span> Actions = &#123;incA, decA&#125;;</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Demo</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">  render() &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; store, actions &#125; = <span class="keyword">this</span>.props;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      &lt;div&gt;</span><br><span class="line">        &lt;p&gt;a = &#123;store.a&#125;&lt;<span class="regexp">/p&gt;</span></span><br><span class="line"><span class="regexp">        &lt;p&gt;</span></span><br><span class="line"><span class="regexp">          &lt;button className="ui-btn" onClick=&#123;actions.incA&#125;&gt;增加 a&lt;/</span>button&gt;</span><br><span class="line">          &lt;button className=<span class="string">"ui-btn"</span> onClick=&#123;actions.decA&#125;&gt;减少 a&lt;<span class="regexp">/button&gt;</span></span><br><span class="line"><span class="regexp">        &lt;/</span>p&gt;</span><br><span class="line">      &lt;<span class="regexp">/div&gt;</span></span><br><span class="line"><span class="regexp">    );</span></span><br><span class="line"><span class="regexp">  &#125;</span></span><br><span class="line"><span class="regexp">&#125;</span></span><br><span class="line"><span class="regexp">/</span><span class="regexp">/ ④将state、actions 映射到组件 props</span></span><br><span class="line"><span class="regexp">const mapStateToProps = state =&gt; (&#123;store: state&#125;);</span></span><br><span class="line"><span class="regexp">const mapDispatchToProps = dispatch =&gt; (&#123;</span></span><br><span class="line"><span class="regexp">  /</span><span class="regexp">/ ⑤bindActionCreators 简化 dispatch</span></span><br><span class="line"><span class="regexp">  actions: bindActionCreators(Actions, dispatch)</span></span><br><span class="line"><span class="regexp">&#125;)</span></span><br><span class="line"><span class="regexp">/</span><span class="regexp">/ ⑥connect产生容器组件</span></span><br><span class="line"><span class="regexp">const Root = connect(</span></span><br><span class="line"><span class="regexp">  mapStateToProps,</span></span><br><span class="line"><span class="regexp">  mapDispatchToProps)(Demo)</span></span><br><span class="line"><span class="regexp">const store = createStore(reducers)</span></span><br><span class="line"><span class="regexp">export default class App extends Component &#123;</span></span><br><span class="line"><span class="regexp">  render() &#123;</span></span><br><span class="line"><span class="regexp">    return (</span></span><br><span class="line"><span class="regexp">      &lt;Provider store=&#123;store&#125;&gt;</span></span><br><span class="line"><span class="regexp">        &lt;Root /</span>&gt;</span><br><span class="line">      &lt;<span class="regexp">/Provider&gt;</span></span><br><span class="line"><span class="regexp">    )</span></span><br><span class="line"><span class="regexp">  &#125;</span></span><br><span class="line"><span class="regexp">&#125;</span></span><br></pre></td></tr></table></figure>

<p>MobX：</p>
<figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React, &#123; Component &#125; <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; observable, action &#125; <span class="keyword">from</span> <span class="string">'mobx'</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; Provider, observer, inject &#125; <span class="keyword">from</span> <span class="string">'mobx-react'</span>;</span><br><span class="line"><span class="comment">// 定义数据结构</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Store</span> </span>&#123;</span><br><span class="line">  <span class="comment">// ① 使用 observable decorator </span></span><br><span class="line">  @observable a = <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 定义对数据的操作</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Actions</span> </span>&#123;</span><br><span class="line">  <span class="keyword">constructor</span>(&#123;store&#125;) &#123;</span><br><span class="line">    <span class="keyword">this</span>.store = store;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// ② 使用 action decorator </span></span><br><span class="line">  @action</span><br><span class="line">  incA = <span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">this</span>.store.a++;</span><br><span class="line">  &#125;</span><br><span class="line">  @action</span><br><span class="line">  decA = <span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">this</span>.store.a--;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// ③实例化单一数据源</span></span><br><span class="line"><span class="keyword">const</span> store = <span class="keyword">new</span> Store();</span><br><span class="line"><span class="comment">// ④实例化 actions，并且和 store 进行关联</span></span><br><span class="line"><span class="keyword">const</span> actions = <span class="keyword">new</span> Actions(&#123;store&#125;);</span><br><span class="line"><span class="comment">// inject 向业务组件注入 store，actions，和 Provider 配合使用</span></span><br><span class="line"><span class="comment">// ⑤ 使用 inject decorator 和 observer decorator</span></span><br><span class="line">@inject(<span class="string">'store'</span>, <span class="string">'actions'</span>)</span><br><span class="line">@observer</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Demo</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">  render() &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; store, actions &#125; = <span class="keyword">this</span>.props;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      &lt;div&gt;</span><br><span class="line">        &lt;p&gt;a = &#123;store.a&#125;&lt;<span class="regexp">/p&gt;</span></span><br><span class="line"><span class="regexp">        &lt;p&gt;</span></span><br><span class="line"><span class="regexp">          &lt;button className="ui-btn" onClick=&#123;actions.incA&#125;&gt;增加 a&lt;/</span>button&gt;</span><br><span class="line">          &lt;button className=<span class="string">"ui-btn"</span> onClick=&#123;actions.decA&#125;&gt;减少 a&lt;<span class="regexp">/button&gt;</span></span><br><span class="line"><span class="regexp">        &lt;/</span>p&gt;</span><br><span class="line">      &lt;<span class="regexp">/div&gt;</span></span><br><span class="line"><span class="regexp">    );</span></span><br><span class="line"><span class="regexp">  &#125;</span></span><br><span class="line"><span class="regexp">&#125;</span></span><br><span class="line"><span class="regexp">class App extends Component &#123;</span></span><br><span class="line"><span class="regexp">  render() &#123;</span></span><br><span class="line"><span class="regexp">    /</span><span class="regexp">/ ⑥使用Provider 在被 inject 的子组件里，可以通过 props.store props.actions 访问</span></span><br><span class="line"><span class="regexp">    return (</span></span><br><span class="line"><span class="regexp">      &lt;Provider store=&#123;store&#125; actions=&#123;actions&#125;&gt;</span></span><br><span class="line"><span class="regexp">        &lt;Demo /</span>&gt;</span><br><span class="line">      &lt;<span class="regexp">/Provider&gt;</span></span><br><span class="line"><span class="regexp">    )</span></span><br><span class="line"><span class="regexp">  &#125;</span></span><br><span class="line"><span class="regexp">&#125;</span></span><br><span class="line"><span class="regexp">export default App;</span></span><br></pre></td></tr></table></figure>

<p>比较一下：</p>
<p><strong>Redux 数据流流动很自然，可以充分利用时间回溯的特征，增强业务的可预测性</strong>；MobX 没有那么自然的数据流动，也没有时间回溯的能力，但是 View 更新很精确，粒度控制很细。 Redux 通过引入一些中间件来处理副作用；<strong>MobX 没有中间件，副作用的处理比较自由，比如依靠 autorunAsync 之类的方法。</strong> Redux 的样板代码更多，看起来就像是我们要做顿饭，需要先买个调料盒装调料，再买个架子放刀叉。。。做一大堆准备工作，然后才开始炒菜；而 MobX 基本没啥多余代码，直接硬来，拿着炊具调料就开干，搞出来为止。 但其实 Redux 和 MobX 并没有孰优孰劣，Redux 比 Mobx 更多的样板代码，是因为特定的设计约束。如果项目比较小的话，使用 MobX 会比较灵活，<strong>但是大型项目，像 MobX 这样没有约束，没有最佳实践的方式，会造成代码很难维护，各有利弊</strong>。一般来说，小项目建议 MobX 就够了，大项目还是用 Redux 比较合适。</p>
<h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a><strong>总结</strong></h1><p>时光荏苒，岁月如梭。每一个框架或者库只能陪你走一段路，最终都会逝去。留在你心中的，不是一条一条的语法规则，而是一个一个的思想，这些思想才是推动进步的源泉。</p>

    </div>
    
    
    
    
    <div>
      
        <div>
    
        <div style="text-align:center;color: #ccc;font-size:14px;">-------------本文结束<i class="fa fa-paw"></i>感谢您的阅读-------------</div>
    
</div>

      
    </div>
        <div class="reward-container">
  <div>坚持原创技术分享，您的支持将鼓励我继续创作！</div>
  <button onclick="var qr = document.getElementById('qr'); qr.style.display = (qr.style.display === 'none') ? 'block' : 'none';">
    打赏
  </button>
  <div id="qr" style="display: none;">
      
      <div style="display: inline-block;">
        <img src="/images/alipay.png" alt="Li Yudong 支付宝">
        <p>支付宝</p>
      </div>

  </div>
</div>

        

<div>
<ul class="post-copyright">
  <li class="post-copyright-author">
    <strong>本文作者： </strong>Li Yudong
  </li>
  <li class="post-copyright-link">
    <strong>本文链接：</strong>
    <a href="http://example.com/2021/12/06/%E7%9B%92%E9%A9%AC%E6%8A%80%E6%9C%AF%E6%A0%88%E5%88%9D%E5%AD%A6/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93/" title="前端状态管理总结">http://example.com/2021/12/06/盒马技术栈初学/前端状态管理总结/</a>
  </li>
  <li class="post-copyright-license">
    <strong>版权声明： </strong>本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" rel="noopener" target="_blank"><i class="fab fa-fw fa-creative-commons"></i>BY-NC-SA</a> 许可协议。转载请注明出处！
  </li>
</ul>
</div>

    
      <footer class="post-footer">
          <div class="post-tags">
              <a href="/tags/%E5%85%AC%E5%8F%B8%E6%8A%80%E6%9C%AF%E6%A0%88/" rel="tag"># 公司技术栈</a>
          </div>
    
        

    
        
    <div class="post-nav">
      <div class="post-nav-item">
    <a href="/2021/12/02/%E7%9B%92%E9%A9%AC%E6%8A%80%E6%9C%AF%E6%A0%88%E5%88%9D%E5%AD%A6/typeScript%E9%A1%B9%E7%9B%AE%E9%85%8D%E7%BD%AE/" rel="prev" title="TypeScript项目配置">
      <i class="fa fa-chevron-left"></i> TypeScript项目配置
    </a></div>
      <div class="post-nav-item"></div>
    </div>
      </footer>
    
  </article>
  
  
  



          </div>
          

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
          <div class="post-toc motion-element"><ol class="nav"><li class="nav-item nav-level-1"><a class="nav-link" href="#前言"><span class="nav-number">1.</span> <span class="nav-text">前言</span></a></li><li class="nav-item nav-level-1"><a class="nav-link" href="#Store模式"><span class="nav-number">2.</span> <span class="nav-text">Store模式</span></a></li><li class="nav-item nav-level-1"><a class="nav-link" href="#Flux"><span class="nav-number">3.</span> <span class="nav-text">Flux</span></a></li><li class="nav-item nav-level-1"><a class="nav-link" href="#Redux"><span class="nav-number">4.</span> <span class="nav-text">Redux</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#store"><span class="nav-number">4.1.</span> <span class="nav-text">store</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#Action"><span class="nav-number">4.2.</span> <span class="nav-text">Action</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#Reducer"><span class="nav-number">4.3.</span> <span class="nav-text">Reducer</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#流程"><span class="nav-number">4.4.</span> <span class="nav-text">流程</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#对比-Flux"><span class="nav-number">4.5.</span> <span class="nav-text">对比 Flux</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#中间件"><span class="nav-number">4.6.</span> <span class="nav-text">中间件</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#处理异步"><span class="nav-number">4.6.1.</span> <span class="nav-text">处理异步</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Redux-thunk"><span class="nav-number">4.6.2.</span> <span class="nav-text">Redux-thunk</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Redux-promise"><span class="nav-number">4.6.3.</span> <span class="nav-text">Redux-promise</span></a></li></ol></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#Vuex"><span class="nav-number">5.</span> <span class="nav-text">Vuex</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#Store"><span class="nav-number">5.1.</span> <span class="nav-text">Store</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#Mutation"><span class="nav-number">5.2.</span> <span class="nav-text">Mutation</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#Action-1"><span class="nav-number">5.3.</span> <span class="nav-text">Action</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#对比Redux"><span class="nav-number">5.4.</span> <span class="nav-text">对比Redux</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#React-redux"><span class="nav-number">6.</span> <span class="nav-text">React-redux</span></a></li><li class="nav-item nav-level-1"><a class="nav-link" href="#Redux-saga"><span class="nav-number">7.</span> <span class="nav-text">Redux-saga</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#对比-Redux-thunk"><span class="nav-number">7.1.</span> <span class="nav-text">对比 Redux-thunk</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#Dva"><span class="nav-number">8.</span> <span class="nav-text">Dva</span></a></li><li class="nav-item nav-level-1"><a class="nav-link" href="#MobX"><span class="nav-number">9.</span> <span class="nav-text">MobX</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#对比-Redux"><span class="nav-number">9.1.</span> <span class="nav-text">对比 Redux</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#总结"><span class="nav-number">10.</span> <span class="nav-text">总结</span></a></li></ol></div>
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
    <img class="site-author-image" itemprop="image" alt="Li Yudong"
      src="/images/author.jpg">
  <p class="site-author-name" itemprop="name">Li Yudong</p>
  <div class="site-description" itemprop="description"></div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/archives/">
        
          <span class="site-state-item-count">95</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
            <a href="/categories/">
          
        <span class="site-state-item-count">14</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/tags/">
          
        <span class="site-state-item-count">18</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>
  <div class="links-of-author motion-element">
      <span class="links-of-author-item">
        <a href="https://github.com/tiarmor1" title="GitHub → https:&#x2F;&#x2F;github.com&#x2F;tiarmor1" rel="noopener" target="_blank"><i class="fab fa-github fa-fw"></i>GitHub</a>
      </span>
      <span class="links-of-author-item">
        <a href="mailto:1157019137@qq.com" title="E-Mail → mailto:1157019137@qq.com" rel="noopener" target="_blank"><i class="fa fa-envelope fa-fw"></i>E-Mail</a>
      </span>
  </div>
  <div class="cc-license motion-element" itemprop="license">
    <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" class="cc-opacity" rel="noopener" target="_blank"><img src="/images/cc-by-nc-sa.svg" alt="Creative Commons"></a>
  </div>



      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        

<div class="copyright">
  
  &copy; 2020 – 
  <span itemprop="copyrightYear">2022</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">Li Yudong</span>
</div>

        








      </div>
    </footer>
  </div>

  
  
  <script color='0,0,0' opacity='0.5' zIndex='-1' count='150' src="/lib1/canvas-nest/canvas-nest.min.js"></script>
  <script src="/lib1/anime.min.js"></script>
  <script src="/lib1/velocity/velocity.min.js"></script>
  <script src="/lib1/velocity/velocity.ui.min.js"></script>

<script src="/js/utils.js"></script>

<script src="/js/motion.js"></script>


<script src="/js/schemes/pisces.js"></script>


<script src="/js/next-boot.js"></script>




  




  
<script src="/js/local-search.js"></script>













  

  

  
   <canvas class="fireworks" style="position: fixed;left: 0;top: 0;z-index: 1; pointer-events: none;" ></canvas> 
   <script type="text/javascript" src="//cdn.bootcss.com/animejs/2.2.0/anime.min.js"></script> 
   <script type="text/javascript" src="/js/fireworks.js"></script>
  
</body>
</html>

